package com.abel.bigwater.tlv.model

import com.alibaba.fastjson.annotation.JSONField
import io.netty.buffer.ByteBuf
import org.slf4j.LoggerFactory
import kotlin.experimental.and

/**
 * tag = 1, 2, 3, 4, 6:
 * tag, length, (id, value)+
 * tag = 5:
 * tag, result.
 */
open class TlvData(
        var tag: Short = 0,
        var length: Int = 0,
        /** this field is only for tag(5) */
        var resultCode: Short = ResultCodeType.T0Success.id) : TlvDataApi {

    @JSONField(serialize = false)
    var pieceList: ArrayList<TlvDataPiece> = ArrayList()

    @JSONField(serializeUsing = TlvExceptionSerializer::class)
    var parseResult: TlvException? = null

    protected val log = LoggerFactory.getLogger(TlvData::class.java)

    override fun finalLen(): Int {
        if (!(tag in (1..6))) throw TlvInvalidException("incorrect tag: ${tag}")

        when (tag) {
            ResultCodeParam.TAG -> {
                length = 1 + 1

                return length
            }

            else -> {
                pieceList ?: throw TlvInvalidException("tlv-data-piece can't be empty")

                length = 0

                for (p1 in pieceList ?: pieceList.orEmpty()) {
                    length = length + p1.finalLen()
                }
                return length + 1 + 2
            }
        }
    }

    override fun fill(buf: ByteBuf): ByteBuf {
        // compute length of data
        finalLen()

        when (tag) {
            ResultCodeParam.TAG -> {
                buf.writeByte(tag.toInt())
                buf.writeByte(resultCode.toInt())
            }
            else -> {
                // to avoid incomplete data ~ it may fail.
                with(buf.alloc().buffer()) {
                    buf.writeByte(tag.toInt())

                    // len for sub-nodes
                    val len = length - (1 + 2)
                    writeShort(len)
                    for (p1 in pieceList ?: pieceList.orEmpty()) {
                        p1.fill(this)
                    }

                    buf.writeBytes(this)
                }
            }
        }

        return buf
    }

    /**
     * dedicated buffer.
     */
    override fun parse(buf: ByteBuf): ByteBuf {
        if (buf.readableBytes() < 2) throw TlvInvalidException("not enough bytes: ${buf.readableBytes()}")

        tag = buf.duplicate().readByte().toShort() and 0x7FF
        if (!(tag in (1..6))) throw TlvInvalidException("invalid tag: ${tag}")

        when (tag) {
            ResultCodeParam.TAG -> {
                buf.skipBytes(1)

                resultCode = buf.readByte().toShort() and 0x7FF
            }

            else -> {
                // validate the buffer.
                with(buf.duplicate()) {
                    // tag
                    skipBytes(1)
                    // length
                    val len2 = readShort().toInt() and 0x7FFFF

                    if (len2 < 1 || len2 > 2048) throw TlvInvalidException("invalid length: ${len2}")
                    if (readableBytes() < len2) throw TlvInvalidException("${len2}>${buf.readableBytes()}")
                }

                // tag
                buf.skipBytes(1)
                length = buf.readShort().toInt() and 0x7FFFF

                // prepare to store pieces
                pieceList = ArrayList()

                // just skip these bytes if fail to parse the following tlv-data.
                // buf is used any more then!
                with(buf.readBytes(length)) {
                    while (readableBytes() > 0) {
                        val p1 = TlvDataPiece(tag)
                        pieceList?.add(p1)

                        p1.parse(this)
                    }
                }
            }
        }

        return buf
    }

}
